#include "gwbase/curl_wrapper.h"

#include <gwbase/base/mutex.h>
#include <gwbase/base/logging.h>

namespace dicom {

void CurlSetup() {
  static Mutex m;
  static bool inited = false;

  LockGuard l(m);
  if (inited)
    return;

  ::curl_global_init(CURL_GLOBAL_ALL);
  inited = true;
}

void CurlCleanup() {
  ::curl_global_cleanup();
}

void CurlSList::AddString(const std::string& s) {
  list_ = ::curl_slist_append(list_, s.c_str());
}

void CurlSList::AddString(const char* s) {
  list_ = ::curl_slist_append(list_, s);
}

CurlHeaders::~CurlHeaders() {
	if (headers != NULL)
		curl_slist_free_all(headers); 
}

void CurlHeaders::AddHeader(const char *name, uint64_t val) {
	AddHeader(name, Fmt("%ld", val).c_str());
}

void CurlHeaders::AddHeader(const char *name, long val) {
	AddHeader(name, Fmt("%d", val).c_str());
}

void CurlHeaders::AddHeader(const char *name, const std::string &val)
{
	std::string header = name;
	header += ": ";
	header += val;
	headers = curl_slist_append(headers, header.c_str());
}

void CurlHeaders::AddOAuthHeader(const std::string& token) {
  this->AddHeader("Authorization", StringPrintf("Bearer %s", token.c_str()));
}


void CurlForm::AddField(const char* name, const std::string& value) 
{
  CURLFORMcode ret = ::curl_formadd(&first_, &last_, 
                                    CURLFORM_COPYNAME, name,
                                    CURLFORM_COPYCONTENTS, value.c_str(),
                                    CURLFORM_CONTENTSLENGTH, value.size());
  assert (ret == CURL_FORMADD_OK);
}

void CurlForm::AddFile(const char* name, const std::string& file_name,
                       CurlReadFileCallback cb, void* data, int64_t size)
{
  assert (this->file_cb_ == NULL);
  this->file_cb_ = cb;
  this->file_size_ = size;
  this->file_cb_data_ = data;
  CURLFORMcode ret = ::curl_formadd(&first_, &last_, 
                                    CURLFORM_COPYNAME, name,
                                    CURLFORM_FILENAME, file_name.c_str(),
                                    CURLFORM_STREAM, this,
                                    CURLFORM_CONTENTSLENGTH, size);
  assert (ret == CURL_FORMADD_OK);
}

void CurlForm::AddToEasyCurl(CURL* handle) {
  CURLcode ret = ::curl_easy_setopt(handle, CURLOPT_HTTPPOST, first_);
  assert (ret == CURLE_OK);

  if (file_cb_ != NULL) {
    ::curl_easy_setopt(handle, CURLOPT_READFUNCTION, ReadFileCallbackWrapper);
  }
}

int CurlForm::ReadFileCallbackWrapper(void* buf, size_t b, size_t n, void* data) {
  CurlForm* form = static_cast<CurlForm*>(data);
  assert (form != NULL);

  return form->file_cb_(buf, b, n, form->file_cb_data_);
}

void EasyCurl::AddHeaders(CurlHeaders* headers) {
  CURLcode ret = curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers->GetList());
  assert (ret == CURLE_OK);
}

void EasyCurl::SetMethod(const char* method) {
  CURLcode ret = curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, method);
  assert (ret == CURLE_OK);
}

void EasyCurl::SetUrl(const std::string& url) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_URL, url.c_str());
  assert (ret == CURLE_OK);
}

void EasyCurl::SetUrlEncodeForm(const std::string& data) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_POSTFIELDS, data.c_str());
  assert (ret == CURLE_OK);
}

void EasyCurl::SetMultipartForm(CurlForm* form) {
  form->AddToEasyCurl(handle_);
}

void EasyCurl::SetPutFileData(CurlReadFileCallback cb, void* data, size_t size) {
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_READFUNCTION, cb);
  assert (ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_READDATA, data);
  assert (ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_UPLOAD, 1U);
  assert (ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_CUSTOMREQUEST, "PUT");
  assert (ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_INFILESIZE_LARGE, size);
  assert (ret == CURLE_OK);
}

void EasyCurl::SetWriteFunction(CurlWriteFileCallback cb, void *data) 
{
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, cb);
  assert (ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEDATA, data);
  assert (ret == CURLE_OK);
}

int EasyCurl::WriteToStringCallback(void* buf, size_t b, size_t n, void* stream)
{
  assert (stream != NULL);
  std::string* str = static_cast<std::string*>(stream);
  str->append(static_cast<const char*>(buf), b * n);
  return static_cast<int>(b * n);
}

bool EasyCurl::PerformText(std::string* response) {
  response->clear();
  CURLcode ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, 
                                    &EasyCurl::WriteToStringCallback);
  assert (ret == CURLE_OK);
  ret = ::curl_easy_setopt(handle_, CURLOPT_WRITEDATA, response);
  assert (ret == CURLE_OK);

  ret = curl_easy_perform(handle_);
  return CollectResult(ret);
}

bool EasyCurl::Perform() {
  CURLcode ret = curl_easy_perform(handle_);
  return CollectResult(ret);
}

bool EasyCurl::CollectResult(CURLcode code) 
{
  if (code != CURLE_OK) {
    http_status_ = 0;
    err_msg_ = ::curl_easy_strerror(code);
    LOG_INFO << "CURL Error: " << err_msg_;
    return false;
  }

  err_msg_.clear();
  ::curl_easy_getinfo(handle_, CURLINFO_RESPONSE_CODE, &http_status_);
  return true;
}

EasyCurl::EasyCurl()
  : handle_(NULL) 
{ 
  CurlSetup();
  handle_ = ::curl_easy_init();
  ::curl_easy_setopt(handle_, CURLOPT_NOSIGNAL, 1L);
  assert (handle_ != NULL);
}

EasyCurl::~EasyCurl() {
  ::curl_easy_cleanup(handle_); 
}

void EasyCurl::Reset() {
  ::curl_easy_reset(handle_);
}

void EasyCurl::EnableDebugOutput(bool enabled) {
  ::curl_easy_setopt(handle_, CURLOPT_VERBOSE, (enabled ? 1U : 0U));
}

};
